import BN from 'bignumber.js'
import AbcUtils from 'abc-utils'
import { isArray, isEmpty, sortBy, padStart, find } from 'lodash'
import { Transaction } from 'ethereumjs-tx'
import C2cService from '@/network/service/C2cService'
// import axios from 'axios'

// const walletService = axios.create({})
// const api = process.env.NODE_ENV == 'production' ? 'https://btc1.trezor.io' : '/trezor'
export const BlockchainTool = {
  btc: {
    coinName: 'Bitcoin',
    version: 1,
    lockTime: 0,
    purpose: 44, // 协议
    coin: 0, //     币种
    account: 0, //  账户
    addressN: [
      (44 | 0x80000000) >>> 0,
      (0 | 0x80000000) >>> 0,
      (0 | 0x80000000) >>> 0,
      0,
      0
    ],
    createTx: function(utxos, outs, fee) {
      /**
       * 构建转账数据
       *
       * @utxos
       * @outs  目标地址
       * @fee  手续费
       *
       *
       * 转账的步骤：
       * 1.根据需要转出到的地址和金额，构建outputs
       * 2.根据outputs总额，构建inputs，判断总额是否足够
       * 3.称重，然后根据费率计算手续费
       * 4.得到手续费后，校验inputs是否满足交易所需（inputs >= outputs + fee）
       * 4.1不满足需求则从utxos中取余额出来再次添加到inputs中，然后循环3,4
       * 5.计算是否需要找零（inputs > outputs + fee）
       * 5.1需要找零则在outputs中增加找零地址,由于地址增加，则需要重新执行3,4,5
       * 6.得到最后的交易数据
       * tips:
       * 1.注意BTC个数和聪(Sat)之间的转换
       * 2.utxos建议根据value升序排列
       *
       * @memberof BlockchainTool
       */
      if (!isArray(utxos) || isEmpty(utxos) || !isArray(outs) || isEmpty(outs) || !fee)
        return false
      const inputs = [],
        outputs = []
      let inputAmounts = 0,
        outAmounts = 0
      utxos = sortBy(utxos, 'value')
      const buildOutputs = () => {
        for (let i = 0; i < outs.length; i++) {
          /*构建outputs*/
          let amount = AbcUtils.btc.toSat(outs[i].amount)
          outputs.push({
            address: outs[i].address,
            amount,
            script_type: 'PAYTOADDRESS'
          })
          /*统计总量*/
          outAmounts = BN(outAmounts).plus(amount)
        }
      }
      const weigh = () => {
        /*称重*/
        const sizeIn = BN(inputs.length).times(148)
        const sizeOut = BN(outputs.length).times(34)
        const fees = BN(sizeIn)
          .plus(sizeOut)
          .plus(10)
          .times(fee)
        const total = BN(outAmounts).plus(fees)
        return AbcUtils.btc.toSat(total)
      }
      const createAddressN = path => {
        /*通过path构建addressN*/
        const addressN = []
        path = path.match(/\/[0-9]+('|H)?/g)
        for (let item of path) {
          let id = parseInt(item.match(/[0-9]+/g)[0])
          if (item.match(/('|H)/g)) id = (id | 0x80000000) >>> 0
          addressN.push(id)
        }
        return addressN
      }
      const buildInputs = () => {
        for (let i = 0; i < utxos.length; i++) {
          if (BN(inputAmounts).gte(weigh())) break
          const address_n = createAddressN(utxos[i].path)
          const amount = utxos[i].value
          const prev_hash = utxos[i].txid
          const prev_index = utxos[i].n
          const script_type = 'SPENDP2SHWITNESS'
          const sequence = 4294967295
          inputAmounts = BN(inputAmounts).plus(utxos[i].value)
          inputs.push({
            address_n,
            amount,
            prev_hash,
            prev_index,
            script_type,
            sequence
          })
        }
      }
      buildOutputs()
      buildInputs()
      /*余额不足*/
      if (BN(outAmounts).gt(inputAmounts)) return false
      const countChange = () => {
        /*计算找零*/
        let changeAmounts = BN(inputAmounts)
          .minus(outAmounts)
          .toNumber()
        if (changeAmounts > 0) {
          const address_n = [
            (this.purpose | 0x80000000) >>> 0,
            (this.coin | 0x80000000) >>> 0,
            (this.account | 0x80000000) >>> 0,
            1,
            0
          ]
          const amount = changeAmounts
          const script_type = 'PAYTOADDRESS'
          outputs.push({
            address_n,
            amount,
            script_type
          })
          /*outputs变化，重新称重,并构建inputs*/
          buildInputs()
        }
      }
      countChange()
      return {
        inputs: JSON.stringify(inputs, null, 4),
        outputs: JSON.stringify(outputs, null, 4)
      }
    },
    /*
    BTC多签过程
    从后台获取:1.赎回脚本（内含所有用户的公钥）2.交易源数据（由于解码问题，改为返回未编码的交易源数据）
    * */
    buildTransactionPayload: async function({ inputs, outputs, pubkeys }) {
      /**
       * 构建多签转账数据
       *
       * @param {*} params
       * @param {*} params.inputs
       * @param {*} params.outputs
       * @param {*} params.pubkeys
       *
       * @memberof BlockchainTool
       */
      if (
        !isArray(inputs) ||
        isEmpty(inputs) ||
        !isArray(outputs) ||
        isEmpty(outputs) ||
        !isArray(pubkeys) ||
        isEmpty(pubkeys)
      ) {
        throw new Error('待签名数据异常,请核对后再试')
      }
      const utxoHashes = this.getReferencedTransactionHashs(inputs)
      const utxoTxs = await this.getReferencedTransactions(utxoHashes)
      const utxos = this.transformUtxos(utxoTxs)
      const multiInputs = this.transformInputs(inputs, pubkeys, utxoTxs)
      const multiOutputs = this.transformOutputs(outputs)
      const payload = {
        coin_name: this.coinName,
        inputs: multiInputs,
        outputs: multiOutputs,
        utxo: utxos,
        version: this.version,
        lock_time: this.lockTime
      }
      return payload
    },
    getReferencedTransactionHashs: function(inputs) {
      const legacyInputs = inputs.filter(input => !input.segwit)
      if (legacyInputs.length < 1) {
        return []
      }
      return legacyInputs.reduce((result, input) => {
        if (result.includes(input.prevHash)) return result
        return result.concat(input.prevHash)
      }, [])
    },
    getReferencedTransactions: async function(hashes) {
      return Promise.all(hashes.map(hash => this.getTransaction(hash)))
    },
    getTransaction: async function(hash) {
      // const { data } = await walletService.get(`${api}/api/v2/tx/${hash}`)
      const { data } = await C2cService.fromHashToSign(hash)
      console.log('fromHashToSign返回的数据:  ', data)
      return data
    },
    transformUtxos: function(txs) {
      return txs.map(tx => {
        return {
          version: tx.version,
          hash: tx.txid,
          inputs: tx.vin.map(input => {
            return {
              prev_index: input.vout,
              sequence: input.sequence,
              prev_hash: input.txid,
              script_sig: input.hex
            }
          }),
          bin_outputs: tx.vout.map(output => {
            return {
              amount:
                typeof output.value === 'number' ? output.value.toString() : output.value,
              script_pubkey: output.hex
            }
          }),
          lock_time: 0
        }
      })
    },
    transformInputs: function(txs, pubkeys, utxoTxs) {
      return txs.map(tx => {
        return {
          address_n: [
            (this.purpose | 0x80000000) >>> 0,
            (this.coin | 0x80000000) >>> 0,
            (this.account | 0x80000000) >>> 0
          ],
          prev_hash: tx.prevHash,
          prev_index: tx.prevIndex,
          sequence: tx.sequence,
          script_type: 'SPENDMULTISIG',
          amount: find(utxoTxs, { txid: tx.prevHash }).value,
          multisig: {
            pubkeys: pubkeys.map(pubkey => {
              return {
                node: pubkey,
                address_n: []
              }
            }),
            signatures: ['', '', '', ''],
            m: 4
          }
        }
      })
    },
    transformOutputs: function(txs) {
      return txs.map(tx => {
        return {
          address: tx.recipientAddress,
          amount: AbcUtils.btc.toSat(tx.value),
          script_type: 'PAYTOADDRESS'
        }
      })
    },
    createAddressN: function(path) {
      /*通过path构建addressN*/
      const addressN = []
      path = path.match(/\/[0-9]+('|H)?/g)
      for (let item of path) {
        let id = parseInt(item.match(/[0-9]+/g)[0])
        if (item.match(/('|H)/g)) id = (id | 0x80000000) >>> 0
        addressN.push(id)
      }
      return addressN
    }
  },
  usdt: {
    purpose: 44, // 协议
    coin: 60, //     币种
    account: 0, //  账户
    chainId: 1,
    addressN: [
      (44 | 0x80000000) >>> 0,
      (60 | 0x80000000) >>> 0,
      (0 | 0x80000000) >>> 0,
      0,
      0
    ],
    contract: '0xdac17f958d2ee523a2206206994597c13d831ec7', //主网-合约地址
    testContract: '0xc71cb026d39d10f34b88d73ca4cca85aab2db20c', //测试网-合约地址
    usdtBulkContract: '0xaeeb9fb132f2606b5958f82855f67418ef5d569b', //主网-USDT在ERC20批量转账自定义合约@李林
    bulkContract: '0xB2eD49c471ff87A3393752BCd42288795B2db891', //主网-ERC20代币批量转账自定义合约@李林
    testBulkContract: '0x6234b67f19c57fdcc7308521a07af42168347011', //测试网-ERC20代币批量转账自定义合约@李林
    transferOrder: 'a9059cbb', //transfer(address,uint256) 普通转账指令
    multiSendOrder: '9ec68f0f', //multiSend(address,address[],uint256[]) 批量转账指令
    approveOrder: '095ea7b3', //approve(address,uint256) 授权批量转账合约指令
    /**
     * USDT 创建erc20需要的data
     *
     * @address 目标地址
     * @amount 转账金额
     *
     * transfer(address,uint256)
     * 创建过程
     * 1.转账地址去掉0x，然后左侧补零满64位，得到dataAddress
     * 2.金额转为eth单位(Gwei)，然后转16进制（以1e-18为单位）去掉0x后，再左侧补零满64位，得到dataAmount
     * 3.拼接data：头部：erc20指令hash：a9059cbb；中：dataAddress，尾部：dataAmount
     *
     * @memberof BlockchainTool
     * */
    createErc20Data: function(address, amount) {
      let to = address.replace('0x', '')
      let dataAddress = padStart(to, 64, '0')
      let _amount = AbcUtils.eth.toHex(AbcUtils.eth.toWei(amount, 'Mwei')).substr(2)
      let dataAmount = padStart(_amount, 64, '0')
      return `${this.transferOrder}${dataAddress}${dataAmount}`
    },
    /**
     * USDT 创建批量转账合约授权需要的data
     *
     * @amounts 允许合约转账的总金额  默认一个亿
     *
     * approve(address,uint256)
     *
     * @memberof BlockchainTool
     * */
    createErc20Approve: function(amounts = 100000000) {
      let dataContract = padStart(this.usdtBulkContract.replace('0x', ''), 64, '0')
      let _amount = AbcUtils.eth.toHex(AbcUtils.eth.toWei(amounts, 'Mwei')).substr(2)
      let dataAmount = padStart(_amount, 64, '0')
      return `${this.approveOrder}${dataContract}${dataAmount}`
    },
    /**
     * USDT批量转账创建data
     *
     * @contract 合约地址
     * @txs 转账列表[{address,amount}]
     *
     * multiSend(address,address[],uint256[])
     * data的拼接遵循下面的规则
     * ABI编码规则：http://www.jouypub.com/2018/1292c65cfbe128f290fb336d930d3bca/
     * */
    createErc20BulkData: function(txs) {
      if (!isArray(txs) || isEmpty(txs)) return false
      const len = txs.length
      const addressOffset = 3 * 32 // argument*32
      const amountOffset = addressOffset + 32 + len * 32
      const contractHexData = padStart(this.contract.replace('0x', ''), 64, '0')
      const lenHexData = padStart(AbcUtils.eth.toHex(len).substr(2), 64, '0')
      const addressOffsetHexData = padStart(
        AbcUtils.eth.toHex(addressOffset).substr(2),
        64,
        '0'
      )
      const amountOffsetHexData = padStart(
        AbcUtils.eth.toHex(amountOffset).substr(2),
        64,
        '0'
      )
      let addressData = ''
      let amountData = ''
      let addressDataArray = []
      let amountDataArray = []
      for (let i = 0; i < len; i++) {
        /*USDT按照10^6转为wei，对应Mwei*/
        console.log(AbcUtils.eth.toWei(txs[i].amount, 'Mwei'))
        let _amount = AbcUtils.eth
          .toHex(AbcUtils.eth.toWei(txs[i].amount, 'Mwei'))
          .substr(2)
        console.log(_amount)
        let _address = txs[i].address.replace('0x', '')
        amountData += padStart(_amount, 64, '0')
        addressData += padStart(_address, 64, '0')
        addressDataArray.push(_address)
        amountDataArray.push(_amount)
      }
      return `${
        this.multiSendOrder
      }${contractHexData}${addressOffsetHexData}${amountOffsetHexData}${lenHexData}${addressData}${lenHexData}${amountData}`
    },
    /**
     * 创建交易源数据
     *
     * exp:
     * data = {
        nonce: '0x00',
        gasPrice: '0x09184e72a000',
        gasLimit: '0x2710',
        to: '0x0000000000000000000000000000000000000000',
        value: '0x00',
        data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057',
        v: '0x1c',
        r: '0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab',
        s: '0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13',
      }
     * */
    createRawData: function(data) {
      return new Transaction(data).serialize().toString('hex')
    },
    test: function() {}
  }
}
